﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace NeuralTest
{
    using System.Collections.Concurrent;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Threading.Tasks;



    [TestClass]
    public class NMath
    {

        List<double> Doubles = new List<double>() { 2.2, 4.6, 44.2, 77.3, 1.5, 2.2, 44.5, 88.2, 1212.2, 1.4 };
        /// <summary>
        ///  Calculates the variance over a double array.
        /// </summary>
        /// <param name="input"> The input. </param>
        /// <returns>
        ///  The calculated variance.
        /// </returns>
        public static double CalculateVariance(double[] input)
        {
            double squareSum = 0.0;
            double total = 0.0;
            foreach (double d in input)
            {
                total += d;
                squareSum += Math.Pow(d, 2);
            }
            int n = input.Length;
            return ((n * squareSum) - Math.Pow(total, 2)) / (n * (n - 1));
        }



        public static double CalculateRightVariance(double[] input)
        {
            double sumOfSquares = 0.0;
            double total = 0.0;
            // Using a lambda expression.
            //Parallel.For(0, input.Length, i =>
            //{
            //    // Do Work.

            //});




            /*TODO: Check potentially-changing upper bound expression "input.Length" which is now called only *once*,
            to ensure the new Parallel.For call matches behavior in the original for-loop
           (where this upper bound expression had previously been evaluated at the start of *every* loop iteration).*/
            Parallel.For(
                0,
                input.Length - 1,
                i =>
                {
                    total += input[i];
                    sumOfSquares += Math.Pow(input[i], 2);
                });

            //Parallel.ForEach(
            //    input,
            //    d =>
            //        {
            //            total += d;
            //    sumOfSquares += Math.Pow(d, 2);
            //});
            int n = input.Length;

            return ((n * sumOfSquares) - Math.Pow(total, 2)) / (n * (n - 1));

        }
        public static double CalculateForLoopVariance(double[] input)
        {
            double sumOfSquares = 0.0;
            double total = 0.0;
            for (int i = 0; i < input.Length; i++)
            {
                total += input[i];
                sumOfSquares += Math.Pow(input[i], 2);
            }
            int n = input.Length;
            return ((n * sumOfSquares) - Math.Pow(total, 2)) / (n * (n - 1));
        }

        /// <summary>
        ///  Calculates the variance over a double array.
        /// </summary>
        /// <param name="input"> The input. </param>
        /// <returns>
        ///  The calculated variance.
        /// </returns>
        public static double CalculateVarianceParallel(double[] input)
        {
            double sumOfSquares = 0.0;
            double total = 0.0;
            // Using a lambda expression.
            //Parallel.For(0, input.Length, i =>
            //{
            //    // Do Work.

            //});

            object b = new object();

            /*TODO: Check potentially-changing upper bound expression "input.Length" which is now called only *once*,
            to ensure the new Parallel.For call matches behavior in the original for-loop
           (where this upper bound expression had previously been evaluated at the start of *every* loop iteration).*/
            lock (b)
            {


                Parallel.For(
                    0,
                    input.Length - 1,
                    i =>
                    {
                        total += input[i];
                        sumOfSquares += Math.Pow(input[i], 2);
                    });
            }

            //Parallel.ForEach(
            //    input,
            //    d =>
            //        {
            //            total += d;
            //    sumOfSquares += Math.Pow(d, 2);
            //});
            int n = input.Length;
            return ((n * sumOfSquares) - Math.Pow(total, 2)) / (n * (n - 1));
        }


        [TestMethod]
        public void VarianceTest()
        {
            HiPerfTimer perfTimer = new HiPerfTimer();
            const int INT_Constant = 5000000;
            for (int x = 0; x < INT_Constant; x++)
            {
                Random r = new Random();
                Doubles.Add(r.NextDouble());

            }
            
            double[] listused = Doubles.ToArray();
            perfTimer.Start();
            //Lets test the variance
            var t = Task.Factory.StartNew(() => CalculateVariance(listused));
            t.Wait();
            Task.Factory.StartNew(() => CalculateVariance(listused));
            t.Wait();
            perfTimer.Stop();

            Console.WriteLine(String.Format("Max duration with task factory :{0} ticks:{1:#.#######} Variance : {2}", perfTimer.Duration, perfTimer.Ticks, t.Result));

            perfTimer.Start();
            //Lets test the variance

            double p = CalculateVariance(listused);
            perfTimer.Stop();

            Console.WriteLine("Max duration with synchronous :{0} ticks :{1} variance : {2}", perfTimer.Duration, perfTimer.Ticks.ToString("#.#######"), p);


            perfTimer.Start();
            //Lets test the variance

            double pfor = CalculateForLoopVariance(listused);
            perfTimer.Stop();

            Console.WriteLine("Max duration with synchronous with For loop:{0} ticks :{1} variance : {2}", perfTimer.Duration, perfTimer.Ticks.ToString("#.#######"), pfor);


            //perfTimer.Start();
            ////Lets test the variance
            //double varparallel = CalculateVarianceParallel(listused);
            //perfTimer.Stop();
            //Console.WriteLine(String.Format("Parallel Max duration :{0} ticks :{1} variance : {2}", perfTimer.Duration, perfTimer.Ticks, varparallel));

            //perfTimer.Start();
            ////Lets test the variance
            //Parallel.Invoke(() => CalculateVarianceParallel(listused));
            //perfTimer.Stop();

            Console.WriteLine(String.Format("Parallel Invoke Max duration :{0} ticks :{1} ", perfTimer.Duration, perfTimer.Ticks));
            perfTimer.Start();
            //Lets test the variance
            var t1 = Task.Factory.StartNew(() => CalculateVarianceParallel(listused));
            t1.Wait();
            perfTimer.Stop();
            Console.WriteLine(String.Format("Parallel Max duration with Task Factory :{0} ticks :{1} variance : {2}", perfTimer.Duration, perfTimer.Ticks, t1.Result));


            //perfTimer.Start();
            ////Lets test the variance
            //var tlinq = Task.Factory.StartNew(() => CalculateRightVariance(listused));


            //var dp = VarianceTest(listused);

            //Console.WriteLine("tt :" + dp);


            



            //perfTimer.Start();
            //var t2 = Task<double>.Factory.StartNew(() =>
            //{
            //    return CalculateVarianceParallel2(bbp);
            //});

            //////Lets test the variance
            ////Parallel.Invoke(
            ////    () =>
            ////    Console.WriteLine(
            ////        String.Format(
            ////            "Concurrent Bag: Invoke Max duration :{0} ticks :{1} Variance Result :{2} ",
            ////            perfTimer.Duration,
            ////            perfTimer.Ticks,
            ////            CalculateVarianceParallel(bbp))));


            //perfTimer.Stop();
            //Console.WriteLine(String.Format("Concurrent Parallel Invoke Max duration :{0} ticks :{1}  {2}", perfTimer.Duration, perfTimer.Ticks, t2.Result));


            perfTimer.Start();
            System.Collections.Concurrent.ConcurrentBag<double> bbp = new ConcurrentBag<double>(listused);
            var taa = Task<double>.Factory.StartNew(() =>
            {
                return CalculateVarianceParallel(bbp);
            });

            ////Lets test the variance
            //Parallel.Invoke(
            //    () =>
            //    Console.WriteLine(
            //        String.Format(
            //            "Concurrent Bag: Invoke Max duration :{0} ticks :{1} Variance Result :{2} ",
            //            perfTimer.Duration,
            //            perfTimer.Ticks,
            //            CalculateVarianceParallel(bbp))));

            taa.Wait();
            taa = Task<double>.Factory.StartNew(() =>
            {
                return CalculateVarianceParallel(bbp);
            });
            taa.Wait();


            perfTimer.Stop();
            Console.WriteLine(String.Format("Concurrent Invoke Max duration :{0} ticks :{1}  {2}", perfTimer.Duration, perfTimer.Ticks.ToString("#.##########"), taa.Result));
        }

        private double CalculateVarianceParallel(ConcurrentBag<double> bbp)
        {
            double squareSum = 0.0;
            double total = 0.0;
            foreach (double d in bbp)
            {
                total += d;
                squareSum += Math.Pow(d, 2);
            }
            int n = bbp.Count;
            return ((n * squareSum) - Math.Pow(total, 2)) / (n * (n - 1));
        }

        private double CalculateVarianceParallel2(ConcurrentBag<double> bbp)
        {
            double squareSum = 0.0;
            double total = 0.0;
            Parallel.ForEach(bbp, d =>
            {
                total += d;
                squareSum += Math.Pow(d, 2);
            });
            int n = bbp.Count;
            return ((n * squareSum) - Math.Pow(total, 2)) / (n * (n - 1));
        }

        private static double VarianceTest(double[] listx)
        {
            var result = listx.AsParallel().Select(item => new { sumtotal = +item, powered = Math.Pow(item, 2) });

            double xx = 0;
            int n = listx.Length;
            foreach (var v in result)
            {
                // Console.WriteLine("Source {0}, Result {1}", v.sumtotal, v.powered);
                //    ((n * sumOfSquares) - Math.Pow(total, 2)) / (n * (n - 1));
                xx += ((n * v.powered) - Math.Pow(v.sumtotal, 2)) / (n * (n - 1));
            }

            return xx;
        }

        /// <summary>
        ///  Hi performance timer / benchmarks, returns the values in seconds of an operation.
        /// </summary>
        public class HiPerfTimer
        {
            [DllImport("Kernel32.dll")]
            private static extern bool QueryPerformanceCounter(
                out long lpPerformanceCount);

            [DllImport("Kernel32.dll")]
            private static extern bool QueryPerformanceFrequency(
                out long lpFrequency);

            private long startTime, stopTime;
            private long freq;

            // Constructor

            public HiPerfTimer()
            {
                startTime = 0;
                stopTime = 0;

                if (QueryPerformanceFrequency(out freq) == false)
                {
                    // high-performance counter not supported

                    throw new Win32Exception("High Perf timer not supported probably...");
                }
            }

            // Start the timer

            public void Start()
            {
                // lets do the waiting threads there work

                Thread.Sleep(0);

                QueryPerformanceCounter(out startTime);
            }

            // Stop the timer

            public void Stop()
            {
                QueryPerformanceCounter(out stopTime);
            }

            // Returns the duration of the timer (in seconds)

            public double Duration
            {
                get
                {
                    return (double)(stopTime - startTime) / (double)freq;
                }
            }

            public double Ticks
            {
                get
                {
                    return ((double)(stopTime - startTime) / (double)freq) / 1000;
                }
            }
        }
    }
}
